////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) Roman Ranzmaier (http://www.ranzmaier.at/contact.aspx).  All rights reserved.
// 
// Codeplex project: http://gadgeteernetworking.codeplex.com
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

using System;
using Microsoft.SPOT;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using Ranzmaier.NETMF.IO;


namespace Ranzmaier.NETMF.GadgeteerNetworking
{
    public class UdpClient : NetClientBase
    {
        private const int _buffersize = 4096;
        private byte[] _Buffer;
        private bool _IsBroadcast;



        public UdpClient()
            :base()
        {
            _Buffer = new byte[_buffersize];
            CreateClientSocket();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="UdpClient"/> class.
        /// </summary>
        /// <param name="ep">The ep.</param>
        public UdpClient(IPEndPoint ep)
            : base(ep)
        {
            _Buffer = new byte[_buffersize];
            CreateClientSocket();

        }

        /// <summary>
        /// Initializes a new instance of the <see cref="UdpClient"/> class.
        /// </summary>
        /// <param name="hostname">The hostname.</param>
        /// <param name="port">The port.</param>
        public UdpClient(string hostname,int port)
            : base()
        {
            _Buffer = new byte[_buffersize];
            CreateClientSocket();
        }
        private void CreateClientSocket()
        {
            _socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
        }


        /// <summary>
        /// Gets or sets a value indicating whether [enable broadcast].
        /// </summary>
        /// <value>
        ///   <c>true</c> if [enable broadcast]; otherwise, <c>false</c>.
        /// </value>
        public bool EnableBroadcast
        {
            get
            {
                return ((int)Client.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast)) == 1;
            }
            set
            {
                Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, value ? 1 : 0);
            }
        }


        private void CheckForBroadcast()
        {
            if (this.Client != null && !_IsBroadcast)
            {
                _IsBroadcast = true;
                Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
            }
        }


        /// <summary>
        /// Connects the specified hostname and port
        /// </summary>
        /// <param name="hostname">The hostname.</param>
        /// <param name="port">The port.</param>
        public override void Connect(string hostname, int port)
        {
            Connect(hostname, port, ProtocolType.Udp);

        }
        /// <summary>
        /// Establishes a default remote host using the specified network endpoint.
        /// </summary>
        /// <param name="ep">The ep.</param>
        /// <exception cref="T:System.Net.Sockets.SocketException">An error occurred when accessing the socket. See the Remarks section for more information. </exception>
        ///   
        /// <exception cref="T:System.ArgumentNullException">
        ///   <paramref name="endPoint"/> is null. </exception>
        ///   
        /// <exception cref="T:System.ObjectDisposedException">The <see cref="T:Ranzmaier.NETMF.GadgeteerNetworking.UDPClient"/> is closed. </exception>
        public override void Connect(IPEndPoint ep)
        {
            if (ep == null)
                throw new ArgumentNullException("endPoint");
            this.CheckForBroadcast();
            this.Client.Connect(ep);
            Active = true;
        }
        /// <summary>
        /// Sends a UDP datagram to the host at the specified remote endpoint.
        /// </summary>
        /// <param name="dgram">An array of type <see cref="T:System.Byte"/> that specifies the UDP datagram that you intend to send, represented as an array of bytes.</param>
        /// <param name="bytes">The number of bytes in the datagram.</param>
        /// <param name="endPoint">An <see cref="T:System.Net.IPEndPoint"/> that represents the host and port to which to send the datagram.</param>
        /// <returns>
        /// The number of bytes sent.
        /// </returns>
        /// <exception cref="T:System.ArgumentNullException">
        ///   <paramref name="dgram"/> is null. </exception>
        ///   
        /// <exception cref="T:System.InvalidOperationException">
        ///   <see cref="T:Ranzmaier.NETMF.GadgeteerNetworking.UDPClient"/> has already established a default remote host. </exception>
        ///   
        /// <exception cref="T:System.ObjectDisposedException">
        ///   <see cref="T:Ranzmaier.NETMF.GadgeteerNetworking.UDPClient"/> is closed. </exception>
        ///   
        /// <exception cref="T:System.Net.Sockets.SocketException">An error occurred when accessing the socket. See the Remarks section for more information. </exception>
        public int Send(byte[] dgram, int bytes, IPEndPoint endPoint)
        {
            if (dgram == null)
                throw new ArgumentNullException("dgram");
            if (Active && endPoint != null)
                throw new InvalidOperationException("Not connected");
            if (endPoint == null)
                return this.Client.Send(dgram, 0, bytes, SocketFlags.None);
            this.CheckForBroadcast();
            return this.Client.SendTo(dgram, 0, bytes, SocketFlags.None, endPoint);
        }


        /// <summary>
        /// Sends a UDP datagram to a specified port on a specified remote host.
        /// </summary>
        /// <param name="dgram">An array of type <see cref="T:System.Byte"/> that specifies the UDP datagram that you intend to send represented as an array of bytes.</param>
        /// <param name="bytes">The number of bytes in the datagram.</param>
        /// <param name="hostname">The name of the remote host to which you intend to send the datagram.</param>
        /// <param name="port">The remote port number with which you intend to communicate.</param>
        /// <returns>
        /// The number of bytes sent.
        /// </returns>
        /// <exception cref="T:System.ArgumentNullException">
        ///   <paramref name="dgram"/> is null. </exception>
        ///   
        /// <exception cref="T:System.InvalidOperationException">The <see cref="T:Ranzmaier.NETMF.GadgeteerNetworking.UDPClient"/> has already established a default remote host. </exception>
        ///   
        /// <exception cref="T:System.ObjectDisposedException">The <see cref="T:Ranzmaier.NETMF.GadgeteerNetworking.UDPClient"/> is closed. </exception>
        ///   
        /// <exception cref="T:System.Net.Sockets.SocketException">An error occurred when accessing the socket. See the Remarks section for more information. </exception>
        public int Send(byte[] dgram, int bytes, string hostname, int port)
        {

            if (dgram == null)
                throw new ArgumentNullException("dgram");
            if (Active && (hostname != null || port != 0))
                throw new InvalidOperationException("Not connected");
            if (hostname == null || port == 0)
                return this.Client.Send(dgram, 0, bytes, SocketFlags.None);

            var hosts = Dns.GetHostEntry(hostname);
            int num = 0;

            if (hosts.AddressList.Length == 0 || num == hosts.AddressList.Length)
            {
                throw new ArgumentException("Invalid List", "hostname");
            }
            this.CheckForBroadcast();
            IPEndPoint remoteEP = new IPEndPoint(hosts.AddressList[num], port);
            return this.Client.SendTo(dgram, 0, bytes, SocketFlags.None, remoteEP);
        }

        /// <summary>
        /// Sends a UDP datagram to a remote host.
        /// </summary>
        /// <param name="dgram">An array of type <see cref="T:System.Byte"/> that specifies the UDP datagram that you intend to send represented as an array of bytes.</param>
        /// <param name="bytes">The number of bytes in the datagram.</param>
        /// <returns>
        /// The number of bytes sent.
        /// </returns>
        /// <exception cref="T:System.ArgumentNullException">
        ///   <paramref name="dgram"/> is null. </exception>
        ///   
        /// <exception cref="T:System.InvalidOperationException">The <see cref="T:Ranzmaier.NETMF.GadgeteerNetworking.UDPClient"/> has already established a default remote host. </exception>
        ///   
        /// <exception cref="T:System.ObjectDisposedException">The <see cref="T:Ranzmaier.NETMF.GadgeteerNetworking.UDPClient"/> is closed. </exception>
        ///   
        /// <exception cref="T:System.Net.Sockets.SocketException">An error occurred when accessing the socket. See the Remarks section for more information. </exception>
        public int Send(byte[] dgram, int bytes)
        {
            if (dgram == null)
                throw new ArgumentNullException("dgram");
            if (!Active)
                throw new InvalidOperationException("Not Connected");
            return this.Client.Send(dgram, 0, bytes, SocketFlags.None);
        }

        /// <summary>
        /// Returns a UDP datagram that was sent by a remote host.
        /// </summary>
        /// <param name="remoteEP">An <see cref="T:System.Net.IPEndPoint"/> that represents the remote host from which the data was sent.</param>
        /// <returns>
        /// An array of type <see cref="T:System.Byte"/> that contains datagram data.
        /// </returns>
        /// <exception cref="T:System.ObjectDisposedException">The underlying <see cref="T:System.Net.Sockets.Socket"/> has been closed. </exception>
        ///   
        /// <exception cref="T:System.Net.Sockets.SocketException">An error occurred when accessing the socket. See the Remarks section for more information. </exception>
        public byte[] Receive(ref IPEndPoint remoteEP)
        {
            EndPoint endPoint = new IPEndPoint(IPAddress.Any, 0);
            int num = this.Client.ReceiveFrom(_Buffer, _buffersize, SocketFlags.None, ref endPoint);
            remoteEP = (IPEndPoint)endPoint;
            if (num < 65536)
            {
                byte[] array = new byte[num];
                Array.Copy(this._Buffer, 0, array, 0, num);
                return array;
            }
            return _Buffer;
        }

        /*
        /// <summary>
        /// Adds a <see cref="T:Ranzmaier.NETMF.GadgeteerNetworking.UDPClient"/> to a multicast group.
        /// </summary>
        /// <param name="multicastAddr">The multicast <see cref="T:System.Net.IPAddress"/> of the group you want to join.</param>
        /// <exception cref="T:System.ObjectDisposedException">The underlying <see cref="T:System.Net.Sockets.Socket"/> has been closed. </exception>
        ///   
        /// <exception cref="T:System.Net.Sockets.SocketException">An error occurred when accessing the socket. See the Remarks section for more information. </exception>
        public void JoinMulticastGroup(IPAddress multicastAddr)
        {

            if (multicastAddr == null)
                throw new ArgumentNullException("multicastAddr");

            this.Client.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastInterface, multicastAddr.GetAddressBytes());
            //this.Client.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, multicastAddr.GetAddressBytes());
        }

        /// <summary>
        /// Leaves a multicast group.
        /// </summary>
        /// <param name="multicastAddr">The <see cref="T:System.Net.IPAddress"/> of the multicast group to leave.</param>
        /// <exception cref="T:System.ObjectDisposedException">The underlying <see cref="T:System.Net.Sockets.Socket"/> has been closed. </exception>
        ///   
        /// <exception cref="T:System.Net.Sockets.SocketException">An error occurred when accessing the socket. See the Remarks section for more information. </exception>
        ///   
        /// <exception cref="T:System.ArgumentNullException">
        ///   <paramref name="multicastAddr"/> is null.</exception>
       
        public void DropMulticastGroup(IPAddress multicastAddr)
        {
            if (multicastAddr == null)
                throw new ArgumentNullException("multicastAddr");
            this.Client.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.DropMembership, multicastAddr.GetAddressBytes());
        }
         * 
         */ 

    }

}
